/*
 * Copyright (C) 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.utils.labeling;

import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;

/**
 * An object that builds and executes a query for creating a new SQLite table.
 */
public class SQLiteTableBuilder {
    private static final String CREATE_TABLE = "CREATE TABLE ";
    private static final String OPEN_PAREN = "(";
    private static final String CLOSE_PAREN = ")";
    private static final String COMMA = ", ";
    private static final String PRIMARY_KEY = " PRIMARY KEY";

    private static final String INTEGER = " INTEGER";
    private static final String REAL = " REAL";
    private static final String TEXT = " TEXT";
    private static final String BLOB = " BLOB";

    /*
     * Valid SQL identifiers are a sequence of uppercase or lowercase letters,
     * numbers, and underscores. They can't be empty or start with a number.
     */
    private static final String REGEX_SQL_IDENTIFIER = "^[a-zA-Z_][a-zA-Z0-9_]*$";

    public static final int TYPE_INTEGER = 1;
    private static final int TYPE_REAL = 2;
    public static final int TYPE_TEXT = 3;
    private static final int TYPE_BLOB = 4;

    private StringBuilder mStringBuilder;
    private SQLiteDatabase mDatabase;
    private boolean mHasColumns = false;
    private boolean mCreated = false;

    /**
     * Creates a new instance for creating a table with the given name in the
     * specified database.
     *
     * @param database The database in which to create the table.
     * @param tableName A SQL identifier for the name of the new table.
     */
    public SQLiteTableBuilder(SQLiteDatabase database, String tableName) {
        if (database == null) {
            throw new IllegalArgumentException("Database cannot be null.");
        } else if (TextUtils.isEmpty(tableName)) {
            throw new IllegalArgumentException("Table name cannot be empty.");
        } else if (!tableName.matches(REGEX_SQL_IDENTIFIER)) {
            throw new IllegalArgumentException("Invalid table name.");
        }

        mDatabase = database;

        mStringBuilder = new StringBuilder();
        mStringBuilder.append(CREATE_TABLE);
        mStringBuilder.append(tableName);
        mStringBuilder.append(OPEN_PAREN);
    }

    /**
     * Adds a column to the current table that is not a primary key.
     *
     * @param columnName A SQL identifier representing the name of the column. A
     *            valid SQL identifier consists of uppercase or lowercase
     *            letters, numbers, and underscores. It cannot be empty or start
     *            with a number.
     * @param type Must be one of {@link #TYPE_INTEGER}, {@link #TYPE_REAL},
     *            {@link #TYPE_TEXT}, or {@link #TYPE_BLOB}.
     * @return the current {@link SQLiteTableBuilder} instance, with the newly
     *         added column.
     */
    public SQLiteTableBuilder addColumn(String columnName, int type) {
        return addColumn(columnName, type, false);
    }

    /**
     * Adds a column to the current table.
     *
     * @param columnName A SQL identifier representing the name of the column. A
     *            valid SQL identifier consists of uppercase or lowercase
     *            letters, numbers, and underscores. It cannot be empty or start
     *            with a number.
     * @param type Must be one of {@link #TYPE_INTEGER}, {@link #TYPE_REAL},
     *            {@link #TYPE_TEXT}, or {@link #TYPE_BLOB}.
     * @param primaryKey Whether or not the column is a primary key.
     * @return the current {@link SQLiteTableBuilder} instance, with the newly
     *         added column.
     */
    public SQLiteTableBuilder addColumn(String columnName, int type, boolean primaryKey) {
        if (TextUtils.isEmpty(columnName)) {
            throw new IllegalArgumentException("Column name cannot be empty.");
        } else if (!columnName.matches(REGEX_SQL_IDENTIFIER)) {
            throw new IllegalArgumentException("Invalid column name.");
        }

        if (mHasColumns) {
            mStringBuilder.append(COMMA);
        }

        mStringBuilder.append(columnName);
        appendType(type);
        if (primaryKey) {
            mStringBuilder.append(PRIMARY_KEY);
        }

        mHasColumns = true;

        return this;
    }

    private void appendType(int type) {
        switch (type) {
            case TYPE_INTEGER:
                mStringBuilder.append(INTEGER);
                break;
            case TYPE_REAL:
                mStringBuilder.append(REAL);
                break;
            case TYPE_TEXT:
                mStringBuilder.append(TEXT);
                break;
            case TYPE_BLOB:
                mStringBuilder.append(BLOB);
                break;
            default:
                throw new IllegalArgumentException("Unrecognized data type.");
        }
    }

    /**
     * @return The query string to execute in order to create the table.
     */
    String buildQueryString() {
        return String.format("%s%s", mStringBuilder.toString(), CLOSE_PAREN);
    }

    /**
     * Executes the query to create the table.
     * <p>
     * Note: This method can only be called once per instance of this class.
     */
    public void createTable() throws SQLException {
        if (mCreated) {
            throw new IllegalStateException("createTable was already called on this instance.");
        }

        mDatabase.execSQL(buildQueryString());
        mCreated = true;
    }
}
